package com.sfdk.graphics.engine
{
    import com.sfdk.graphics.core.SpriteEx;
    import com.sfdk.graphics.engine.api.IGraphicElement;
    import com.sfdk.graphics.engine.api.IGraphicRenderer;
    import com.sfdk.graphics.engine.api.ILayerInformation;
    import com.sfdk.graphics.engine.api.IMapObject;
    import com.sfdk.graphics.engine.core.TileRenderer;
    import com.sfdk.graphics.engine.core.enums.RendererType;
    import com.sfdk.utils.IntPoint;

    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Point;
    import flash.geom.Rectangle;

    public class GraphicEngine extends Sprite
    {
        private var _tileSize:int;
        private var _screenWidth:int;
        private var _screenHeight:int;

        private var _displayScreenLower:Bitmap;
        private var _displayScreenJoueur:Bitmap;
        private var _displayScreenUpper:Bitmap;
        private var _lowerDisplayScreenData:BitmapData;
        private var _middleDisplayScreenData:BitmapData;
        private var _upperDisplayScreenData:BitmapData;

        private var _gridLayer:SpriteEx;
        private var _overGridLayer : SpriteEx;

        private var _cameraObject:IGraphicElement;
        private var _cameraMoving:int;
        private var _cameraSpeedX:Number;
        private var _cameraSpeedY:Number;

        private var _paintElement:IGraphicElement;

        private var _centerTileX:int;
        private var _centerTileY:int;
        private var _scale:Number;

        private var _renderer:IGraphicRenderer;
        private var _copyRect:Rectangle;
        private const _elementList:Vector.<IGraphicElement> = new Vector.<IGraphicElement>();
        private var _isLoaded:Boolean;
        private var _mapObject:IMapObject;
        private const _basePoint:Point = new Point();
        private var _cameraHadMoved:Boolean;
        private var _displayWalkable:Boolean;

        public function GraphicEngine(screenWidth:int, screenHeight:int, scale:Number = 1, displayWalkable:Boolean = true):void {
            _screenWidth = screenWidth;
            _screenHeight = screenHeight;
            _scale = scale;
            _displayWalkable = displayWalkable;

            _lowerDisplayScreenData = new BitmapData(screenWidth, screenHeight, true, 0x00555566);
            _displayScreenLower = new Bitmap(_lowerDisplayScreenData);
            _displayScreenLower.scaleX = scale;
            _displayScreenLower.scaleY = scale;

            _middleDisplayScreenData = new BitmapData(screenWidth, screenHeight, true, 0x00555566);
            _displayScreenJoueur = new Bitmap(_middleDisplayScreenData);
            _displayScreenJoueur.scaleX = scale;
            _displayScreenJoueur.scaleY = scale;

            _upperDisplayScreenData = new BitmapData(screenWidth, screenHeight, true, 0x00555566);
            _displayScreenUpper = new Bitmap(_upperDisplayScreenData);
            _displayScreenUpper.scaleX = scale;
            _displayScreenUpper.scaleY = scale;

            _gridLayer = new SpriteEx();
            _gridLayer.mouseEnabled = false;
            _gridLayer.scaleX = _scale;
            _gridLayer.scaleY = _scale;

            _overGridLayer = new SpriteEx();
            _overGridLayer.mouseEnabled = false;
            _overGridLayer.scaleX = _scale;
            _overGridLayer.scaleY = _scale;

            _centerTileX = (int(_screenWidth / 24) >> 1) * 24;
            _centerTileY = (int(_screenHeight / 24) >> 1) * 24;

            _copyRect = new Rectangle(0, 0, _screenWidth, _screenHeight);
            _tileSize = 24;

            _gridLayer.visible = false;

            addChild(_displayScreenLower);
            addChild(_gridLayer);
            addChild(_overGridLayer);
            addChild(_displayScreenJoueur);
            addChild(_displayScreenUpper);

            drawGrid();
        }

        public function resizeScreen(screenWidth:int, screenHeight:int, resetPosition:Boolean = false):void {
            _screenWidth = screenWidth;
            _screenHeight = screenHeight;

            _lowerDisplayScreenData = new BitmapData(screenWidth, screenHeight, true, 0x00555566);
            _displayScreenLower.bitmapData = _lowerDisplayScreenData;

            _middleDisplayScreenData = new BitmapData(screenWidth, screenHeight, true, 0x00555566);
            _displayScreenJoueur.bitmapData = _middleDisplayScreenData;

            _upperDisplayScreenData = new BitmapData(screenWidth, screenHeight, true, 0x00555566);
            _displayScreenUpper.bitmapData = _upperDisplayScreenData;

            _copyRect.width = _screenWidth;
            _copyRect.height = _screenHeight;

            _centerTileX = (int(_screenWidth / 24 / _scale) >> 1) * 24;
            _centerTileY = (int(_screenHeight / 24 / _scale) >> 1) * 24;

            if (resetPosition) {
                _copyRect.x = 0;
                _copyRect.y = 0;
            }
            update();
            drawGrid();
        }

        public function loadMap(mapObject:IMapObject):void {
            if (mapObject.rendererType.kind == RendererType.TILE_BASED.kind)
                _renderer = new TileRenderer() as IGraphicRenderer;

            _mapObject = mapObject;

//			_copyRect.x = 0;
//			_copyRect.y = 0;

            _renderer.addEventListener("layerBuilt", renderView);
            _renderer.loadMap(mapObject);
        }

        public function drawGrid():void {
            var graphics:Graphics = _gridLayer.graphics;
            graphics.clear();
            graphics.lineStyle(1, 0, 0.5);
            var tileWidth:int = _screenWidth / _tileSize;
            var tileHeight:int = _screenHeight / _tileSize;
            var offsetX:Number = scrollX % _tileSize
            var offsetY:Number = scrollY % _tileSize
            var i:int, j:int;
            for (i = 0; i <= tileWidth; i++) {
                graphics.moveTo(- offsetX + i * 24, 0);
                graphics.lineTo(- offsetX + i * 24, _screenHeight);
            }
            for (i = 0; i <= tileHeight; i++) {
                graphics.moveTo(0, - offsetY + i * 24);
                graphics.lineTo(_screenWidth, - offsetY + i * 24);
            }
        }

        public function getThumbnail():BitmapData {
            return _renderer.getThumbnail();
        }

        public function forceRefresh(layer:ILayerInformation = null, usedTiles:Vector.<IntPoint> = null):void {
            _renderer.forceRefresh(layer, usedTiles);
        }

        public function renderView(e:Event):void {
            _renderer.removeEventListener("layerBuilt", renderView);
            _isLoaded = true;
            _mapObject.cleanTilesets();
            update(null, true);
        }

        public function refreshView():void {
            //			_lowerDisplayScreenData.lock();
            //			_middleDisplayScreenData.lock();
            //			_upperDisplayScreenData.lock();
            //			_lowerDisplayScreenData.fillRect(new Rectangle(0 , 0 , screenWidth_ , screenHeight_) , 0x00FFFFFF);
            //			_middleDisplayScreenData.fillRect(new Rectangle(0 , 0 , screenWidth_ , screenHeight_) , 0x00FFFFFF);
            //			_upperDisplayScreenData.fillRect(new Rectangle(0 , 0 , screenWidth_ , screenHeight_) , 0x00FFFFFF);
            //			_renderer.copySolBitmap(_lowerDisplayScreenData , _copyRect , _basePoint);
            //			_renderer.copyJoueurBitmap(_middleDisplayScreenData , _copyRect , _basePoint);
            //			_renderer.copyDessusBitmap(_upperDisplayScreenData , _copyRect , _basePoint);
            //			_renderer.copyWalkableBitmap(_upperDisplayScreenData , _copyRect , _basePoint);
            //			_lowerDisplayScreenData.unlock();
            //			_middleDisplayScreenData.unlock();
            //			_upperDisplayScreenData.unlock();
            //			dispatchEvent(new Event("mapRefreshed"));
        }

        public function scroll(xPos:int, yPos:int):void {
            _copyRect.x = xPos * 24;
            _copyRect.y = yPos * 24;
            refreshView();
        }

        public function get scrollX():Number {
            return _copyRect.x;
        }

        public function get scrollY():Number {
            return _copyRect.y;
        }


        public function addElement(graphicElement:IGraphicElement, setAsCamera:Boolean = false):void {
            if (setAsCamera)
                _cameraObject = graphicElement;
            _elementList.push(graphicElement);
        }

        public function setAsCamera(graphicElement:IGraphicElement, smoothTransition:Boolean = false):void {
            _cameraObject = graphicElement;
            _cameraObject.isOnScreen = true;
            var position:Point;
            position = _cameraObject.tilePosition;
            if (position.x > (_screenWidth >> 1) || position.x < (_mapObject.width - (_screenWidth >> 1))) { // Center X
                var diffX:int = _centerTileX - (position.x - _copyRect.x);
            }
            if (position.y > (_screenHeight >> 1) || position.y < (_mapObject.height - (_screenHeight >> 1))) { // Center Y
                var diffY:int = _centerTileY - (position.y - _copyRect.y);
            }
            if (!smoothTransition) {
                _copyRect.x -= diffX;
                _copyRect.y -= diffY;
            }
            else {
                _cameraMoving = 10;
                _cameraSpeedX = diffX / 10;
                _cameraSpeedY = diffY / 10;
            }
        }

        public function setPaintElement(paintElement:IGraphicElement):void {
            _paintElement = paintElement;
        }

        private var _upperLayerCutRect:Vector.<IntPoint>;

        public function update(scrollDirection:Point = null, forceRender:Boolean = false, forceElements : Boolean = false):void {
            if (scrollDirection != null) {
                _copyRect.x += scrollDirection.x * 24;
                _copyRect.y += scrollDirection.y * 24;
            }
            if (_isLoaded) {
                var position:Point;
                var cameraMoved:Boolean = forceRender;
                var topSpeed:int = 2;
                var normalSpeed:int = 1;
                if (_cameraObject != null) {
                    position = _cameraObject.tilePosition;
                    topSpeed = 2;
                    if (_cameraMoving) {
                        _copyRect.x -= _cameraSpeedX;
                        _copyRect.y -= _cameraSpeedY;
                        _cameraMoving--;
                        cameraMoved = true;
                    }
                    else
                    {
                        var diffX:Number = (_centerTileX - (position.x - _copyRect.x));
                        var diffY:Number = (_centerTileY - (position.y - _copyRect.y));
                        // Allow for a 3x3 square around the center
                        if (diffX != 0 && (diffX < -_tileSize || diffX > _tileSize)) {
                            if (diffX >= (3 * _tileSize) || diffX <= -(3 * _tileSize))
                                _copyRect.x -= topSpeed * (diffX / Math.abs(diffX));
                            else
                                _copyRect.x -= normalSpeed * (diffX / Math.abs(diffX));
                            cameraMoved = true;
                        }
                        if (diffY != 0 && (diffY < -_tileSize || diffY > _tileSize)) {
                            if (diffY >= 3 * _tileSize || diffY <= -(3 * _tileSize))
                                _copyRect.y -= topSpeed * (diffY / Math.abs(diffY));
                            else
                                _copyRect.y -= normalSpeed * (diffY / Math.abs(diffY));
                            cameraMoved = true;
                        }
                    }
                    if (!cameraMoved && _cameraHadMoved && !isNaN(diffX) && (diffX % 24) != 0) {
                        if (diffX >= 1 || diffX <= -1)
                            _copyRect.x -= diffX / Math.abs(diffX);
                        else
                            _copyRect.x -= Math.abs(diffX) * (diffX / Math.abs(diffX));

                        cameraMoved = true;
                    }
                    if (!cameraMoved && _cameraHadMoved && !isNaN(diffY) && (diffY % 24) != 0) {
                        if (diffY >= 1 || diffY <= -1)
                            _copyRect.y -= diffY / Math.abs(diffY);
                        else
                            _copyRect.y -= Math.abs(diffY) * (diffY / Math.abs(diffY));
                        cameraMoved = true;
                    }
                    if (cameraMoved) {
                        _copyRect.x = Math.min(Math.max(0, _copyRect.x), _mapObject.width - _screenWidth);
                        _copyRect.y = Math.min(Math.max(0, _copyRect.y), _mapObject.height - _screenHeight);
                    }
                    _cameraHadMoved = cameraMoved;
                }

                // Locking the bitmapdata
                _lowerDisplayScreenData.lock();
                _middleDisplayScreenData.lock();
                _upperDisplayScreenData.lock();

                if (cameraMoved) {
                    _lowerDisplayScreenData.fillRect(new Rectangle(0, 0, _screenWidth, _screenHeight), 0x00FFFFFF);
                    _upperDisplayScreenData.fillRect(new Rectangle(0, 0, _screenWidth, _screenHeight), 0x00FFFFFF);
                }
                _middleDisplayScreenData.fillRect(new Rectangle(0, 0, _screenWidth, _screenHeight), 0x00FFFFFF);

                if (cameraMoved)
                    _renderer.copySolBitmap(_lowerDisplayScreenData, _copyRect, _basePoint);

                _renderer.copyJoueurBitmap(_middleDisplayScreenData, _copyRect, _basePoint);
                var elementTmp:IGraphicElement;
                var isCleaned:Boolean = false;
                var i:int = _elementList.length;

                while (--i >= 0) {
                    elementTmp = _elementList[i];
                    if (forceElements || (elementTmp.isVisible && elementTmp != _cameraObject && (elementTmp.isOnScreen || elementTmp.hasMoved))) {
                        elementTmp.hasChanged = false;
                        //						if(!isCleaned)
                        //						{
                        //							isCleaned = true;
                        //						}
                        position = elementTmp.position.clone();
                        position.x -= _copyRect.x;
                        position.y -= _copyRect.y;
                        if ((elementTmp.isOnScreen && !elementTmp.hasMoved) ||
                                ((forceElements || elementTmp.hasMoved) && position.x >= 0 && position.y >= 0 && position.x < _copyRect.width && position.y < _copyRect.height)) {
                            _middleDisplayScreenData.copyPixels(elementTmp.view, elementTmp.view.rect, position, null, position, true);
                            elementTmp.isOnScreen = true;
                        }
                        else {
                            elementTmp.isOnScreen = false
                        }
                        elementTmp.hasMoved = false;
                    }
                }

                if(_cameraObject != null && _cameraObject.isOnScreen && _cameraObject.isVisible)
                {
                    position = _cameraObject.position.clone();
                    position.x -= _copyRect.x;
                    position.y -= _copyRect.y;
                    _middleDisplayScreenData.copyPixels(_cameraObject.view, _cameraObject.view.rect, position, null,  position, true);
                }



                if (_paintElement && _paintElement.isOnScreen) {
                    position = _paintElement.position.clone();
                    position.x -= _copyRect.x;
                    position.y -= _copyRect.y;

                    _middleDisplayScreenData.copyPixels(_paintElement.view, _paintElement.view.rect, position);
                    elementTmp.isOnScreen = true
                }
                if (_cameraObject) {
                    position = _cameraObject.position;
                    var upperLayerCutRect:Vector.<IntPoint> = _mapObject.getUpperLayerCutRect(position.x / _tileSize, position.y / _tileSize);
                    if (upperLayerCutRect != null && (_upperLayerCutRect == null || upperLayerCutRect[0].equals(_upperLayerCutRect[0])))
                        _renderer.forceRefresh(_mapObject.upperLayers[1], upperLayerCutRect, true);
                    else if (upperLayerCutRect == null && _upperLayerCutRect != null)
                        _renderer.forceRefresh(_mapObject.upperLayers[1], _upperLayerCutRect, false);
                    _upperLayerCutRect = upperLayerCutRect;
                }

                if (cameraMoved)
                    _renderer.copyDessusBitmap(_upperDisplayScreenData, _copyRect, _basePoint);
                if (_displayWalkable)
                    _renderer.copyWalkableBitmap(_upperDisplayScreenData, _copyRect, _basePoint);

                // Unlock the bitmapdata
                _lowerDisplayScreenData.unlock();
                _middleDisplayScreenData.unlock();
                _upperDisplayScreenData.unlock();

            }
            if (gridLayer.visible)
                drawGrid();
        }

        public function getView():BitmapData {
            var outBitmapData:BitmapData = new BitmapData(_screenWidth, _screenHeight);
            _renderer.copySolBitmap(outBitmapData, _copyRect, _basePoint);
            _renderer.copyJoueurBitmap(outBitmapData, _copyRect, _basePoint);
            _renderer.copyDessusBitmap(outBitmapData, _copyRect, _basePoint);
            _renderer.copyWalkableBitmap(outBitmapData, _copyRect, _basePoint);
            return outBitmapData;
        }

        public function get maxWidth():int {
            if (_renderer == null)
                return 0;
            return _renderer.mapWidth;
        }

        public function get maxHeight():int {
            if (_renderer == null)
                return 0;
            return _renderer.mapHeight;
        }

        public function get gridLayer():SpriteEx {
            return _gridLayer;
        }

        public function get overGridLayer():SpriteEx {
            return _overGridLayer;
        }

        public function removeElement(graphicElement:IGraphicElement):void {
            _elementList.splice(_elementList.indexOf(graphicElement), 1);
        }
    }
}